정적 컴파일을 활용한 아이콘 렌더링
다음과 같이 컴파일 시간에 정적 svg 아이콘들을 읽어 속성을 추가 가능한 svg로 변환하고, 파일 목록에 있는 아이콘들을 바로 타입으로 환산할 수 있다.
defmodule DeopjibWebUI.Parts.Icon do
use DeopjibWeb, :html
# 컴파일 시간에 아이콘 목록 가져오기
@icons_path Path.join(:code.priv_dir(:deopjib), "static/icons")
@icons @icons_path
|> File.ls!()
|> Enum.filter(&String.ends_with?(&1, ".svg"))
|> Enum.map(&String.replace(&1, ".svg", ""))
|> Enum.map(&String.to_atom/1)
@icon_contents @icons
|> Enum.map(fn icon ->
path = Path.join(@icons_path, "#{icon}.svg")
{icon, File.read!(path)}
end)
|> Map.new()
attr(:name, :atom, required: true, values: @icons)
attr(:class, :string, default: nil)
attr(:rest, :global, default: %{})
def render(assigns) do
svg_content =
process_svg_content(
Map.get(@icon_contents, assigns.name, "<svg></svg>"),
assigns.class,
assigns.rest
)
assigns = assign(assigns, :svg_content, Phoenix.HTML.raw(svg_content))
~H"""
<%= @svg_content %>
"""
end
# SVG 처리 함수
defp process_svg_content(svg, class, rest) do
# SVG 태그의 열림 부분과 내용 분리
{opening_tag, content} =
case Regex.run(~r/(<svg[^>]*>)(.*)<\/svg>/s, svg) do
[_, tag, inner] ->
inner =
Regex.replace(
~r/stroke="([^"]*)"|fill="([^"]*)"|stroke-width="([^"]*)"/s,
inner,
fn _ ->
""
end
)
{tag, inner}
_ ->
{"<svg>", ""}
end
# 클래스 추가
opening_tag =
if class && class != "" do
if String.match?(opening_tag, ~r/class="[^"]*"/) do
Regex.replace(~r/class="([^"]*)"/, opening_tag, fn _, orig ->
~s(class="#{orig} #{class}")
end)
else
String.replace(opening_tag, "<svg", ~s(<svg class="#{class}"))
end
else
opening_tag
end
# 나머지 속성 추가
opening_tag =
Enum.reduce(rest, opening_tag, fn {key, value}, acc ->
attr_name = key |> Atom.to_string() |> String.replace("_", "-")
if String.match?(acc, ~r/#{attr_name}="[^"]*"/) do
Regex.replace(~r/#{attr_name}="[^"]*"/, acc, ~s(#{attr_name}="#{value}"))
else
String.replace(acc, "<svg", ~s(<svg #{attr_name}="#{value}"))
end
end)
# 최종 SVG 생성
opening_tag <> content <> "</svg>"
end
def icons, do: @icons
end
사용시
<Icon.render name={:trash} class="fill-primary stroke-white" />